<?php
/**
 * 定义f:form控件
 * 
 * 可以使用的参数有：
 * - error.render <boolean> 是否显示所包含控件的错误
 * - error.bundle <array> 错误的消息绑定
 * - controller <string> 接收表单的控制器名
 * - action <string> 接收表单的动作名，可以包含控制器名，如 log.add
 * - param.* <array> 接受表单的动作参数
 * - credit.cache <boolean> 是否缓存credit，默认为true
 * 
 * error.bundle类似于：
 * <code>
 * messages = array(
 * 	"username" => array(
 *		"required" => "必须要填啊",
 * 		"word" => "必须是单词啊"
 *	)
 * );
 * </code>
 *
 * @since 1.1.0
 * @package if
 * @subpackage module.ui
 */
class UIForm extends UIControl {
	protected $tpl = "form";
	private $elements = null;
	private $isValid = true;
	private $posted = false;

	function _after_parse() {
		parent::_after_parse();
		
		$this->addReserveAttrs(array( "param", "error", "controller", "action", "credit" ));
		
		$method = $this->attr("method");
		if (!$method) {
			$this->setAttr("method", "post");
		}
		
		//action & param
		if ($this->attr("controller") || $this->attr("action") || $this->attr("param")) {
			$this->setAttr("action", $this->controller->path($this->attr("controller"), $this->attr("action"), $this->attr("param")));
		}
	}
	
	/**
	 * 取得表单中的所有元素控件
	 *
	 * @return array
	 * @since 1.1.0
	 */
	function elements() {
		if (is_null($this->elements)) {
			$this->elements = array();
			$this->_findElement($this);
		}
		return $this->elements;
	}
	
	/**
	 * 取得表单的唯一ID
	 *
	 * @param string $id 预设的ID
	 * @return string
	 * @since 1.1.0
	 */
	function uniqueId($id = null) {
		return "form:" . UIPage::instance()->uniqueId() . ($id ? $id : $this->id());
	}
	
	/**
	 * 绘制表单
	 *
	 * @return string
	 * @since 1.1.0
	 */
	function render() {
		$credit = null;
		$formId = $this->uniqueId();
		if ($this->can("credit.cache", false)) {
			try {
				$credit = ICache::engine()->read($formId);
			} catch (ICacheException $e) {
				$this->_log($e->getMessage(), ILog::LOG_WARNING);
			}
		}
		if (!$credit) {
			$credit = array(
				"id" => $this->id(),
				"elements" => array(),
				"error" => $this->attr("error"),
				"class" => get_class($this)
			);
			foreach ($this->elements() as $id => $element) {
				$credit["elements"][$id] = array( 
					"name" => $element->name(),
					"_name" => $element->attr("name") 
				);
				$validators = $element->validators();
				if (!empty($validators)) {
					$credit["elements"][$id]["validators"] = $validators;
				}
			}
			$credit = IApplication::singleton()->encrypt($credit);
			
			if ($this->can("credit.cache", false)) {
				try {
					ICache::engine()->write($formId, $credit);
				} catch (ICacheException $e) {
					$this->_log($e->getMessage(), ILog::LOG_WARNING);
				}
			}
		}
		
		$this->addTplVar("credit", $credit);
		return parent::render();
	}
	
	/**
	 * 根据认证字符串加载表单
	 *
	 * @param string|null $formId 表单ID
	 * @return UIForm
	 * @since 1.1.0
	 */
	static function initFromCredit($formId = null) {
		$credit = x("UI_FORM_CREDIT");
		$credit = IApplication::singleton()->decrypt($credit);
		if (!$credit) {
			$form = new UIForm();
			$form->setId("");//初始化
			$form->setValid(false);
			return $form;
		}
		if (!is_null($formId) && $credit["id"] != $formId) {
			$form = new UIForm();
			$form->setId("");//初始化
			$form->setValid(false);
			return $form;
		}
		$form = new $credit["class"];
		$form->setPosted(true);
		
		//创建Form
		$form->setId($credit["id"]);
		$form->setAttr("error", $credit["error"]);
		$form->addToContainer();
		
		//增加元素
		foreach ($credit["elements"] as $id => $element) {
			$class = UIControl::classByName($element["name"]);
			$control = new $class();
			$control->setId($id);
			$control->setName($element["name"]);
			$control->addToContainer();
			$control->setAttr("name", $element["_name"]);
			if (isset($element["validators"])) {
				$control->setValidators($element["validators"]);
			}
			$form->addElement($control);
		}
		$form->isValid();//校验
		return $form;
	}
	
	/**
	 * 检验当前表单是否有效
	 *
	 * @return boolean
	 * @since 1.1.0
	 */
	function isValid() {
		if (!$this->isValid) {
			return false;
		}
		foreach ($this->elements() as $element) {
			$element->validate();
		}
		$errors = $this->errors();
		return empty($errors) && $this->isPosted();
	}
	
	/**
	 * 设置当前表单数据是否是合法的
	 *
	 * @param boolean $valid 是否合法
	 * @since 1.1.0
	 */
	function setValid($valid = true) {
		$this->isValid = $valid;
	}
	
	/**
	 * 取得素有错误信息
	 *
	 * @param boolean $first 是否只显示第一条
	 * @return array
	 */
	function errors($first = true) {
		$errors = array();
		foreach ($this->elements() as $element) {
			if ($element->hasErrors()) {
				$errors = array_merge($errors, $first ? array($element->firstError()) : $element->errors());
			}
		}
		return $errors;
	}
	
	/**
	 * 添加表单元素控件
	 *
	 * @param UIFormElement $control 表单元素控件
	 * @since 1.1.0
	 */
	function addElement(UIFormElement $control) {
		$this->elements[$control->id()] = $control;
		$control->setForm($this);
	}
	
	/**
	 * 取得表单元素
	 *
	 * @param string|integer $idOrIndex 元素ID或位置(从0开始)
	 * @return UIFormElement
	 * @since 1.1.0
	 */
	function element($idOrIndex) {
		if (is_int($idOrIndex)) {
			$elements = array_values($this->elements);
			return isset($elements[$idOrIndex]) ? $elements[$idOrIndex] : null;
		}
		return isset($this->elements[$idOrIndex]) ? $this->elements[$idOrIndex] : null;
	}
	
	/**
	 * 设置当前表单是否已被提交
	 *
	 * @param boolean $posted 是否已被提交
	 * @since 1.1.0
	 */
	function setPosted($posted = true) {
		$this->posted = $posted;
	}
	
	/**
	 * 判断当前表单是否被提交
	 *
	 * @return boolean
	 * @since 1.1.0
	 */
	function isPosted() {
		return $this->posted;
	}
	
	private function _findElement(UIControl $control) {
		foreach ($control->children() as $child) {
			if (if_is_instance_of($child, "UIFormElement")) {
				$this->elements[$child->id()] = $child;
			}
			$this->_findElement($child);
		}
	}
	
	/**
	 * 查找符合条件的一个元素
	 *
	 * 支持的查询方法有：
	 * - #id
	 * - tagName
	 * - *
     * - *[@attr]
	 * - *[@attr='xxxx']
	 * 
	 * 如果找不到则返回一个null
	 * 
	 * @param string $query 查询条件
	 * @since 1.1.2
	 * @return UIFormElement
	 */
	function find($query) {
		$ret = $this->findAll($query);
		return empty($ret) ? null : array_shift($ret);
	}
	
	/**
	 * 查找所有符合条件的元素
	 *
	 * 支持的查询方法有：
	 * - #id
	 * - tagName
	 * - *
     * - *[@attr]
	 * - *[@attr='xxxx']
	 * 
	 * 可以一次性查询多个控件：
	 * - $form->findAll("#mypass,f:field[name='password']");
	 * 
	 * @param string $query 查询条件
	 * @since 1.1.2
	 * @return array
	 */
	function findAll($query) {
		if (is_int($query)) {
			$element = $this->element($query);
			return $element ? array( $element ) : array();
		}
		if (if_empty($query)) {
			return array();
		}
		if ($query == "*") {
			return $this->elements();
		}
		$ret = array();
		$tags = preg_split("/\s*(?!<\\\),\s*/", $query);
		foreach ($tags as $tag) {
			if ($tag["0"] == "#") {
				$id = substr($tag, 1);
				$element = $this->element($id);
				if ($element) {
					$ret[$element->id()] = $element;
				}
			}
			elseif (preg_match("/^(\S+)\[@(\S+)=(\"|')(.+)\\3\]$/", $tag, $match)) {
				foreach ($this->elements() as $element) {
					if (($match[1] == "*" || $match[1] == $element->name()) && 
							($element->attr($match[2]) == $match[4])) {
						$ret[$element->id()] = $element;			
					}
				}
			}
			elseif (preg_match("/^(\S+)\[@(\S+)\]$/", $tag, $match)) {
				foreach ($this->elements() as $element) {
					if (($match[1] == "*" || $match[1] == $element->name()) && 
							($match[2] == "*" || !if_empty($element->attr($match[2])))) {
						$ret[$element->id()] = $element;			
					}
				}
			}
			elseif (preg_match("/\S*:\S*/", $tag, $match)) {
				foreach ($this->elements() as $element) {
					if ($element->name() == $tag) {
						$ret[$element->id()] = $element;
					}
				}
			}
			else {
				foreach ($this->elements() as $element) {
					if ($element->attr("name") == $tag) {
						$ret[$element->id()] = $element;
					}
				}
			}
 		}
		
		return $ret;
	}
}


?>